Template operators differ from JavaScript in compatibility mode
-
What you'll see
JavaScript that is correct in an API file behaves differently in a
.hashpage:#{ let h = 5381; for (let i = 0; i < text.length; i++) { h = (h ^= text.charCodeAt(i)) * 33; // ^= is a comparison here in a .hash page } }#No error, no warning —
hgets a meaningless value, becauseh ^= …inside an expression is a comparison. The same code in#/API/changeshas expected.Related symptoms in the same family:
let y = ~!x;fails to compile withparsing failed of operator '~!'.if (a ^!b)is read as "adoes not containb", not asa ^ (!b).
-
What's actually happening
.hashpages outside#/API/run in compatibility mode: plain JavaScript plus Docly's older template syntax. That syntax includes text operators made of two characters:Operator In compatibility mode In plain JavaScript a ^= bacontainsb(case-sensitive)XOR assignment a ~= bacontainsb(not case-sensitive)syntax error a ^! badoes not containb(case-sensitive)a ^ (!b)a ~! badoes not containb(not case-sensitive)syntax error a ^ b,~aXOR, NOT XOR, NOT The pair wins whenever the two characters touch. Inside an expression,
h ^= xis therefore a comparison: it evaluates totrueorfalse, andhis not assigned. That is why there is no error. The one exception ish ^= x;standing alone on a line: since October 2026 (#29249) that is an XOR assignment in both modes. Before that it was also a comparison, and the line did nothing — code that relied on that no-op changes behaviour.A single
^or~has no template meaning, so it is plain bitwise XOR and NOT in both modes. (Older Docly versions rejected a bare^or~in a.hashpage withUnknown token '^' in expression. If you still see that error, write the XOR as((a | b) - (a & b)), or move the code to an API file.) -
What to do
Write the assignment out. It works the same in both modes:
h = h ^ text.charCodeAt(i); // not: h ^= ... h = ((h * 33) ^ text.charCodeAt(i)) >>> 0; // a typical string hashSeparate characters you mean separately:
a ^ !bwith a space, and~(!x)instead of~!x.On a new site, avoid the pitfall entirely by starting in the strictest mode — see Start new sites in strict JavaScript mode.
Or turn compatibility mode off, and the page is plain JavaScript, exactly like an API file:
- For the site's
.hashpages:"hashjs": { "compatibilityMode": false }in#/site.json. See Site.json config file. - For a template stored in a schema: tick Modern syntax in that template's settings in the schema editor.
Turning it off also removes the bracket conditions (
#[Field]#…#[/Field]#), the other text operators ($=and friends), case-insensitive function names and.Length/.Count. Check that the pages compile before switching a live site.When a .hash page computes something different from the same code in an API file, suspect a two-character operator first. Test the expression on its own in a scratch page — see Use a scratch hash file to test Docly functions.
- For the site's
Last reviewed 07 Oct 2026